Skip to main content

第 1 课:张量操作

PyTorch 中, torch.Tensor 是存储和变换数据的主要工具。
Tensor 和NumPy的多维数组非常类似。然而,Tensor 提供GPU计算和自动求梯度等更多功能,

我们通常将数据以张量的形式进行表示,比如我们用三维张量表示一个RGB图像,四维张量表示视频。

我们可以将标量视为零阶张量,矢量可以视为一阶张量,矩阵就是二阶张量。

张量维度代表含义
0维张量代表的是标量(数字)
1维张量代表的是向量
2维张量代表的是矩阵
3维张量时间序列数据 股价 文本数据 单张彩色图片(RGB)

注意:通常我们调用 torch.tensor 进行创建张量,不是调用 torch.Tensor
torch.Tensor 是Python的一个类, torch.tensor 是一个函数。

Tensor主要有以下八个主要属性,data,dtype,shape,device,grad,grad_fn,is_leaf,requires_grad。

  • data:多维数组,最核心的属性,其他属性都是为其服务的;

  • dtype:多维数组的数据类型

  • shape:多维数组的形状

  • device: tensor所在的设备,cpu或cuda;

  • grad,grad_fn,is_leaf,requires_grad: 用于梯度计算

张量的属性还有很多,大家可以通过Pycharm的debug功能进行查看



(一) 创建张量

常见的构造Tensor的方法:

函数功能
tensor(data)类似于np.array
ones(sizes)全1
zeros(sizes)全0
eye(sizes)对角为1,其余为0
arange(s,e,step)从s到e,步长为step
linspace(s,e,steps)从s到e,均匀分成step份
rand/randn(sizes)rand是[0,1)均匀分布;randn是服从N(0,1)的正态分布
normal(mean,std)正态分布(均值为mean,标准差是std)
randperm(m)随机排列

1. 直接构建张量

标准格式如下:

torch.tensor(data, dtype=None, device=None, requires_grad=False, pin_memory=False)
  • data(array_like) tensor的初始数据,可以是list, tuple, numpy array, scalar或其他类型。

  • dtype(torch.dtype, optional) tensor的数据类型,如torch.uint8, torch.float, torch.long等

  • device (torch.device, optional) 决定tensor位于cpu还是gpu。默认为 cpu。

  • requires_grad (bool, optional) 决定是否需要计算梯度。

  • pin_memory (bool, optional) 是否将tensor存于锁页内存。这与内存的存储方式有关,通常为False。

import torch
import numpy as np


t_from_list = torch.tensor( [[1., -1.], [1., -1.]] )
t_from_array= torch.tensor( np.array([[1, -1], [1,-1]]))

print(t_from_list, t_from_list.dtype)
print(t_from_array, t_from_array.dtype)
tensor([[ 1., -1.],
[ 1., -1.]]) torch.float32
tensor([[ 1, -1],
[ 1, -1]]) torch.int64

2. 创建全零张量

标准格式如下:

torch.zeros(*size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
  • layout(torch.layout, optional) - 张量在内存中采用何种布局方式,如torch.strided, torch.sparse_coo等。

  • out(tensor, optional) - 输出的tensor,即该函数返回的tensor可以通过out进行赋值

import torch
o_t = torch.tensor([1])
t = torch.zeros((3, 3), out=o_t)


print(t)
print(o_t)
print(id(t), id(o_t))
# 可以看到,通过torch.zeros创建的张量不仅赋给了t,同时赋给了o_t,并且这两个张量是共享同一块内存,只是变量名不同。
tensor([[0, 0, 0],
[0, 0, 0],
[0, 0, 0]])
tensor([[0, 0, 0],
[0, 0, 0],
[0, 0, 0]])
140209880685520 140209880685520

创建全一矩阵和其他矩阵的方法相同

torch.ones(size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
torch.full(size, fill_value, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)


3. 创建等差的1维张量

标准格式如下:

torch.arange(start=0, end, step=1, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)

张量长度为 (end-start)/step,需要注意数值区间为[start, end)。 主要参数:

  • start (Number) – 数列起始值,默认值为0。the starting value for the set of points. Default: 0.
  • end (Number) – 数列的结束值。
  • step (Number) – 数列的等差值,默认值为1。
  • out (Tensor, optional) – 输出的tensor,即该函数返回的tensor可以通过out进行赋值。
import torch
print(torch.arange(1, 2.51, 0.5))
tensor([1.0000, 1.5000, 2.0000, 2.5000])

4. 创建均分的1维张量

标准格式如下

torch.linspace(start, end, steps=100, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)

张量长度为steps,区间为[start, end]。

主要参数:

  • start (float) – 数列起始值。
  • end (float) – 数列结束值。
  • steps (int) – 数列长度。
print(torch.linspace(3, 10, steps=5))
print(torch.linspace(1, 5, steps=3))
tensor([ 3.0000,  4.7500,  6.5000,  8.2500, 10.0000])
tensor([1., 3., 5.])

5. 创建对数均分的1维张量

标准格式如下

torch.logspace(start, end, steps=100, base=10.0, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)

张量长度为steps, 底为base。

主要参数:

  • start (float) – 确定数列起始值为base^start

  • end (float) – 确定数列结束值为base^end

  • steps (int) – 数列长度。

  • base (float) - 对数函数的底,默认值为10,此参数是在pytorch 1.0.1版本之后加入的。

torch.logspace(start=0.1, end=1.0, steps=5)
torch.logspace(start=2, end=2, steps=1, base=2)
tensor([4.])

6. 创建单位对角矩阵

标准格式如下

torch.eye(n, m=None, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)**

主要参数:

  • n (int) - 矩阵的行数

  • m (int, optional) - 矩阵的列数,默认值为n,即默认创建一个方阵

import torch
print(torch.eye(3))
print(torch.eye(3, 4))
tensor([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
tensor([[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.]])

7. 创建“空”张量

标准格式如下:

torch.empty(*size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False, pin_memory=False)

这里的“空”指的是不会进行初始化赋值操作。

主要参数:

  • size (int...) - 张量维度

  • pin_memory (bool, optional) - pinned memory 又称page locked memory,即锁页内存,该参数用来指示是否将tensor存于锁页内存,通常为False,若内存足够大,建议设置为True,这样在转到GPU时会快一些。


8. 创建“空”张量

标准格式如下

torch.empty_strided(size, stride, dtype=None, layout=None, device=None, requires_grad=False, pin_memory=False)

这里的“空”指的是不会进行初始化赋值操作。

主要参数:

  • stride (tuple of python:ints) - 张量存储在内存中的步长,是设置在内存中的存储方式。

  • size (int...) - 张量维度

  • pin_memory (bool, optional) - 是否存于锁页内存。


9. 高斯分布生成随机数

标准格式如下

torch.normal(mean, std, out=None)

主要参数:

  • mean (Tensor or Float) - 高斯分布的均值,

  • std (Tensor or Float) - 高斯分布的标准差

特别注意事项: mean和std的取值分别有2种,共4种组合,不同组合产生的效果也不同,需要注意

  • mean为张量,std为张量,torch.normal(mean, std, out=None),每个元素从不同的高斯分布采样,分布的均值和标准差由mean和std对应位置元素的值确定;

  • mean为张量,std为标量,torch.normal(mean, std=1.0, out=None),每个元素采用相同的标准差,不同的均值;

  • mean为标量,std为张量,torch.normal(mean=0.0, std, out=None), 每个元素采用相同均值,不同标准差;

  • mean为标量,std为标量,torch.normal(mean, std, size, *, out=None) ,从一个高斯分布中生成大小为size的张量;

import torch
mean = torch.arange(1, 11.)
std = torch.arange(1, 0, -0.1)
normal = torch.normal(mean=mean, std=std)
print("mean: {}, \nstd: {}, \nnormal: {}".format(mean, std, normal))

# 1.3530是通过均值为1,标准差为1的高斯分布采样得来,
# -1.3498是通过均值为2,标准差为0.9的高斯分布采样得来,以此类推
mean: tensor([ 1.,  2.,  3.,  4.,  5.,  6.,  7.,  8.,  9., 10.]), 
std: tensor([1.0000, 0.9000, 0.8000, 0.7000, 0.6000, 0.5000, 0.4000, 0.3000, 0.2000,
0.1000]),
normal: tensor([ 0.5680, 0.8779, 4.1467, 3.7689, 4.7406, 6.2640, 7.2629, 8.2965,
9.0057, 10.0525])

10. 生成均匀分布

标准格式如下:

torch.rand(*size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)

主要参数:

  • size (int...) - 创建的张量的形状

基于已经存在的 tensor,创建一个 tensor

x = x.new_ones(4, 3, dtype=torch.double) 
# 等效于 x = torch.ones(4, 3, dtype=torch.double)
print(x)
x = torch.randn_like(x, dtype=torch.float)  # 重置数据类型为随机张量
print(x)

11. 在区间[low, high)上,生成整数的随机均匀分布

标准格式如下:

torch.randint(low=0, high, size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)

主要参数:

  • low (int, optional) - 下限。

  • high (int) – 上限,主要是开区间。

  • size (tuple) – 张量的形状。


12. 生成形状为size的标准正态分布张量

标准格式如下

torch.randn(*size, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
print(torch.randint(3, 10, (2, 2)))
tensor([[5, 9],
[6, 6]])

13. 生成从0到n-1的随机排列

标准格式如下

torch.randperm(n, out=None, dtype=torch.int64, layout=torch.strided, device=None, requires_grad=False)

14. 生成伯努力分布(0-1分布,两点分布)

标准格式如下

torch.bernoulli(input, *, generator=None, out=None)

主要参数:

  • input (Tensor) - 分布的概率值,该张量中的每个值的值域为[0-1]
import torch
p = torch.empty(3, 3).uniform_(0, 1)
b = torch.bernoulli(p)
print("probability: \n{}, \nbernoulli_tensor:\n{}".format(p, b))
probability: 
tensor([[0.4583, 0.6391, 0.2693],
[0.2647, 0.5919, 0.8923],
[0.9944, 0.3967, 0.3205]]),
bernoulli_tensor:
tensor([[0., 1., 1.],
[0., 0., 1.],
[1., 1., 1.]])


(二) 张量计算

函数名功能描述
cat将多个张量拼接在一起,例如多个特征图的融合可用。
concat同 cat,是 cat() 的别名。
conj返回共轭复数。
chunk将张量在某个维度上分成 n 份。
dsplit类似 numpy.dsplit(),将张量按索引或指定的份数进行切分。
column_stack水平堆叠张量,即第二个维度上增加,等同于 torch.hstack
dstack沿第三个轴进行逐像素(depthwise)拼接。
gather高级索引方法,目标检测中常用于索引 bbox。在指定的轴上,根据给定的 index 进行索引。强烈推荐看 example。
hsplit类似 numpy.hsplit(),将张量按列进行切分。若传入整数,则按等分划分;若传入 list,则按 list 中元素进行索引。
hstack水平堆叠张量,即第二个维度上增加,等同于 torch.column_stack
index_select在指定的维度上,按索引选择数据并拼接成新张量,新张量的指定维度长度是 index 的长度。
masked_select根据 mask(0/1, False/True 形式的 mask)索引数据,返回 1-D 张量。
movedim移动轴,例如 0 和 1 轴交换:torch.movedim(t, 1, 0)
moveaxismovedim,是 torch.movedim() 的别名。
narrow在指定轴上设置起始和长度进行索引,例如:torch.narrow(x, 0, 0, 2) 从第 0 个轴的第 0 元素开始索引 2 个元素。
nonzero返回非零元素的 index,例如:torch.nonzero(torch.tensor([1, 1, 1, 0, 1])) 返回 tensor([[0], [1], [2], [4]])。建议看例子理解。
permute交换轴。
reshape变换形状。
row_stack按行堆叠张量,即第一个维度上增加,等同于 torch.vstack
scattersrc 中的数据根据 index 按照 dim 的方向填进 input 中,适合搭配例子学习。
scatter_addscatter,但对 input 进行元素累加(+=)。
split按给定的大小切分出多个张量,例如:torch.split(a, [1,4])torch.split(a, 2)
squeeze移除张量为 1 的轴,例如:t.shape=[1, 3, 224, 224]t.squeeze().shape -> [3, 224, 224]
stack在新的轴上拼接张量,与 hstackvstack 不同,它新增一个轴,默认从第 0 个轴插入新轴。
swapaxestorch.transpose() 的别名,交换轴。
swapdimstorch.transpose() 的别名,交换轴。
t转置。
take取张量中的某些元素,返回 1D 张量,例如:torch.take(src, torch.tensor([0, 2, 5])) 表示取第 0, 2, 5 个元素。
take_along_dim取张量中的某些元素,返回张量与 index 维度保持一致,常用于对最大概率位置取值或排序。
tensor_split切分张量,核心看 indices_or_sections 参数的设置。
tile将张量重复多次,可按多个维度重复,例如:torch.tile(y, (2, 2))
transpose交换轴。
unbind移除张量的某个轴并返回一组张量,例如:[[1], [2], [3]] --> [1], [2], [3]
unsqueeze增加一个轴,常用于匹配数据维度。
vsplit垂直切分。
vstack垂直堆叠张量。
where根据条件选择 x 的元素还是 y 的元素,拼接成新张量,建议看案例理解。

1. 张量尺寸

print(x.size())  # 返回的torch.Size其实是一个tuple,⽀持所有tuple的操作
print(x.shape)
torch.Size([3, 4])
torch.Size([3, 4])

2. 张量运算

import torch
x = torch.tensor([[1,2,3,4],[5,6,7,8],[9,10,11,12]])
y = torch.ones(3,4)
print("original x:")
print(x)
print("old id:",id(x))
print()

# 方式1, 非在位更新,内存地址改变
x = x + y
print(x)
print("new id:",id(x))
print()

# 方式2, 非在位更新,内存地址改变
x= torch.add(x, y )
print(x)
print("new id:",id(x))
print()

# 方式3, 在位更新,内存地址不变
x.add_( y) 
print(x)
print("new id:",id(x))

# 方式4,在位更新,内存地址不变
x+= y
print(x)
print("new id:",id(x))


original x:
tensor([[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
old id: 140311616886400

tensor([[ 2., 3., 4., 5.],
[ 6., 7., 8., 9.],
[10., 11., 12., 13.]])
new id: 140311616885600

tensor([[ 3., 4., 5., 6.],
[ 7., 8., 9., 10.],
[11., 12., 13., 14.]])
new id: 140311616886720

tensor([[ 4., 5., 6., 7.],
[ 8., 9., 10., 11.],
[12., 13., 14., 15.]])
new id: 140311616886720
tensor([[ 5., 6., 7., 8.],
[ 9., 10., 11., 12.],
[13., 14., 15., 16.]])
new id: 140311616886720

3. 索引计算

import torch
x = torch.tensor([[1,2,3,4],[5,6,7,8],[9,10,11,12]])
print(x)
print("old id:",id(x))

print(x[:, 1]) # 取第二列
print(x[1,1])
print(x[1][1])

x[1][1]=100   # 在位修改,内存地址不变
print(x)
print("new id:",id(x))
tensor([[ 1,  2,  3,  4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
old id: 140311616887440
tensor([ 2, 6, 10])
tensor(6)
tensor(6)
tensor([[ 1, 2, 3, 4],
[ 5, 100, 7, 8],
[ 9, 10, 11, 12]])
new id: 140311616887440

注意:由于 tenser 是较大的数据类型,变量里存储的实际上是指针

因此 索引出来的结果与原数据共享内存,修改一个,另一个会跟着修改。

如果不想修改,可以考虑使用 copy()等方法


import torch
x = torch.tensor([[1,2,3,4],[5,6,7,8],[9,10,11,12]])
old_id=id(x)
print(x)
print("old id:",old_id)

y1 = x[1,1]
y2 = x[1,1]
print('y1 id:',id(y1),"   y1:",y1)
print("y2 id:",id(y2),"   y2:",y2)
print()

y1=100  # 非在位编辑,相当于是重新赋值,分配了一个新的内存地址,与 x 不再关联
print(x)
print("x id = old id:",id(x)==old_id)
print('y1 id:',id(y1),"   y1:",y1)
print()

y2+=100 # 在位编辑,内存地址不变,因此可以直接影响到 x 的值
print(x)
print("x id = old id:",id(x)==old_id)
print("y2 id:",id(y2),"   y2:",y2)


tensor([[ 1,  2,  3,  4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
old id: 140311616888240
y1 id: 140311616890160 y1: tensor(6)
y2 id: 140311616889200 y2: tensor(6)

tensor([[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
x id = old id: True
y1 id: 9769928 y1: 100

tensor([[ 1, 2, 3, 4],
[ 5, 106, 7, 8],
[ 9, 10, 11, 12]])
x id = old id: True
y2 id: 140311616889200 y2: tensor(106)

4. 维度变化

方法一:torch.view

顾名思义,view()仅仅是改变了对这个张量的观察角度

torch.view() 返回的新tensor与源tensor共享内存(其实是同一个tensor)

import torch
x = torch.tensor([[1,2,3,4],[5,6,7,8],[9,10,11,12]])
old_id=id(x)
print(x)
print("old id:",old_id)

y = x.view(12)
z = x.view(-1, 3) # -1是指这一维的维数自动计算得到
print('x size:',x.size(),"  x id:",id(x))
print('y size:',y.size(),"  y id:",id(y))
print('z size:',z.size(),"  z id:",id(z))

y+=1
print("After y+1, x =")
print(x)
print("new id:",id(x))  
tensor([[ 1,  2,  3,  4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
old id: 140311616886160
x size: torch.Size([3, 4]) x id: 140311616886160
y size: torch.Size([12]) y id: 140311616891840
z size: torch.Size([4, 3]) z id: 140311616891760
After y+1, x =
tensor([[ 2, 3, 4, 5],
[ 6, 7, 8, 9],
[10, 11, 12, 13]])
new id: 140311616886160

方法二:torch.reshape

不一定会分配新的内存地址,此函数并不能保证返回的是其拷贝值,所以官方不推荐使用。

如果要让新张量与原来的无关,建议先用 clone() 创造一个副本,然后再使用 torch.view()

import torch
x = torch.tensor([[1,2,3,4],[5,6,7,8],[9,10,11,12]])
old_id=id(x)
print(x)
print("old id:",old_id)


y=x.reshape(1, 12)  # 改变形状为 3x4
y+=1
print("y:",y)
print(x)
print("new id:",id(x))  
print()

# 注:使用 `clone()` 还有一个好处是会被记录在计算图中,即梯度回传到副本时也会传到源 Tensor 。
tensor([[ 1,  2,  3,  4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
old id: 140311616889840
y: tensor([[ 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]])
tensor([[ 2, 3, 4, 5],
[ 6, 7, 8, 9],
[10, 11, 12, 13]])
new id: 140311616889840

此外要注意:张量是否连续

张量的连续性 是指它在内存中是否是按 行优先 存储的,也就是 每一行在内存中紧挨着上一行

如果张量在内存中不按行优先存储,即数据不再是紧邻存储的,则称该张量为 不连续张量。

不连续张量 通常是由于 transpose、permute方法,索引切片操作 所导致的

  • 张量连续时:reshape 和 view 的功能完全相同

  • 张量不连续:reshape 分配新地址,view会报错

# 连续张量
import torch
x = torch.arange(12).reshape(3, 4)
print(x)
print("Is x contiguous:", x.is_contiguous())
print("old id:",id(x))

x = x.view(2, 6)
x = x.reshape(2, 6)
print("new x id:", id(x))
print("new x id:", id(x))
print()



# 非连续张量
x = torch.arange(12).reshape(3, 4)
x = x.transpose(0, 1)  # 转置后,张量变为非连续
print(x)
print("Is x contiguous:", x.is_contiguous())
print("old id:",id(x))
try:
    z_view = x.view(6, 2)
    print(z_view)
except RuntimeError as e:
    print("z_view failed")
z_reshape = x.reshape(6, 2)
print("z_reshape id:", id(z_reshape))
print()


# 非连续张量转为连续张量
print("Is x contiguous (Before):", x.is_contiguous())
x = x.contiguous()
print("Is x contiguous (After) :", x.is_contiguous())
try:
    z_view = x.view(6, 2)
    print("z_view works")
except:
    print("z_view failed")


tensor([[ 0,  1,  2,  3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
Is x contiguous: True
old id: 140311616887760
new x id: 140311616894880
new x id: 140311616894880

tensor([[ 0, 4, 8],
[ 1, 5, 9],
[ 2, 6, 10],
[ 3, 7, 11]])
Is x contiguous: False
old id: 140315192453040
z_view failed
z_reshape id: 140311616891360

Is x contiguous (Before): False
Is x contiguous (After) : True
z_view works

所以,在编写代码时:

如果可以确保张量是连续的,优先使用 view,性能更高。

如果不确定张量是否连续,使用 reshape 更安全。


5. 矩阵归零

标准格式如下:

torch.zeros_like(input, dtype=None, layout=None, device=None, requires_grad=False)
import torch
x = torch.tensor([[1,2,3,4],[5,6,7,8],[9,10,11,12]])
old_id=id(x)
print(x)
print("old id:",old_id)

x= torch.zeros_like(x)  # 在位操作,内存地址不变
print(x)
print("new id:",old_id)
tensor([[ 1,  2,  3,  4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]])
old id: 140311616891680
tensor([[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0]])
new id: 140311616891680

除了创建全0还有创建全1的tensor,使用方法是一样的

torch.ones_like(input, dtype=None, layout=None, device=None, requires_grad=False)
torch.full_like(input, fill_value, out=None, dtype=None, layout=torch.strided, device=None, requires_grad=False)
torch.empty_like(input, dtype=None, layout=None, device=None, requires_grad=False)
torch.rand_like(input, dtype=None, layout=None, device=None, requires_grad=False)
torch.randint_like(input, low=0, high, dtype=None, layout=torch.strided, device=None, requires_grad=False)
torch.randn_like(input, dtype=None, layout=None, device=None, requires_grad=False)

6. 张量转float

# 如果我们有一个元素 `tensor` ,我们可以使用 `.item()` 来获得这个 `value`,而不获得其他性质:
import torch
x = torch.randn(1) 
print(type(x)) 
print(type(x.item()))
<class 'torch.Tensor'>
<class 'float'>

7. 广播机制

x = torch.arange(1, 3).view(1, 2)
print(x)
y = torch.arange(1, 4).view(3, 1)
print(y)
print(x + y)
tensor([[1, 2]])
tensor([[1],
[2],
[3]])
tensor([[2, 3],
[3, 4],
[4, 5]])